// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/views/page_switcher.h"

#include <algorithm>

#include "base/macros.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/pagination_model.h"
#include "ui/base/ui_base_switches_util.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/skia_util.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/layout/box_layout.h"

namespace app_list {

namespace {

    const int kPreferredHeight = 58;

    const int kMaxButtonSpacing = 18;
    const int kMinButtonSpacing = 4;
    const int kMaxButtonWidth = 68;
    const int kMinButtonWidth = 28;
    const int kButtonHeight = 6;
    const int kButtonCornerRadius = 2;
    const int kButtonStripPadding = 20;

    class PageSwitcherButton : public views::CustomButton {
    public:
        explicit PageSwitcherButton(views::ButtonListener* listener)
            : views::CustomButton(listener)
            , button_width_(kMaxButtonWidth)
            , selected_range_(0)
        {
        }
        ~PageSwitcherButton() override { }

        void SetSelectedRange(double selected_range)
        {
            if (selected_range_ == selected_range)
                return;

            selected_range_ = selected_range;
            SchedulePaint();
        }

        void set_button_width(int button_width) { button_width_ = button_width; }

        // Overridden from views::View:
        gfx::Size GetPreferredSize() const override
        {
            return gfx::Size(button_width_, kButtonHeight);
        }

        void OnPaint(gfx::Canvas* canvas) override
        {
            if (state() == STATE_HOVERED)
                PaintButton(canvas, kPagerHoverColor);
            else
                PaintButton(canvas, kPagerNormalColor);
        }

    private:
        void OnGestureEvent(ui::GestureEvent* event) override
        {
            CustomButton::OnGestureEvent(event);

            if (!switches::IsTouchFeedbackEnabled())
                return;

            if (event->type() == ui::ET_GESTURE_TAP_DOWN)
                SetState(views::CustomButton::STATE_HOVERED);
            else if (event->type() == ui::ET_GESTURE_TAP_CANCEL || event->type() == ui::ET_GESTURE_TAP)
                SetState(views::CustomButton::STATE_NORMAL);
            SchedulePaint();
        }

        // Paints a button that has two rounded corner at bottom.
        void PaintButton(gfx::Canvas* canvas, SkColor base_color)
        {
            gfx::Rect rect(GetContentsBounds());
            rect.ClampToCenteredSize(gfx::Size(button_width_, kButtonHeight));

            SkPath path;
            path.addRoundRect(gfx::RectToSkRect(rect),
                SkIntToScalar(kButtonCornerRadius),
                SkIntToScalar(kButtonCornerRadius));

            SkPaint paint;
            paint.setAntiAlias(true);
            paint.setStyle(SkPaint::kFill_Style);
            paint.setColor(base_color);
            canvas->DrawPath(path, paint);

            int selected_start_x = 0;
            int selected_width = 0;
            if (selected_range_ > 0) {
                selected_width = selected_range_ * rect.width();
            } else if (selected_range_ < 0) {
                selected_width = -selected_range_ * rect.width();
                selected_start_x = rect.right() - selected_width;
            }

            if (selected_width) {
                gfx::Rect selected_rect(rect);
                selected_rect.set_x(selected_start_x);
                selected_rect.set_width(selected_width);

                SkPath selected_path;
                selected_path.addRoundRect(gfx::RectToSkRect(selected_rect),
                    SkIntToScalar(kButtonCornerRadius),
                    SkIntToScalar(kButtonCornerRadius));
                paint.setColor(kPagerSelectedColor);
                canvas->DrawPath(selected_path, paint);
            }
        }

        int button_width_;

        // [-1, 1] range that represents the portion of the button that should be
        // painted with kSelectedColor. Positive range starts from left side and
        // negative range starts from the right side.
        double selected_range_;

        DISALLOW_COPY_AND_ASSIGN(PageSwitcherButton);
    };

    // Gets PageSwitcherButton at |index| in |buttons|.
    PageSwitcherButton* GetButtonByIndex(views::View* buttons, int index)
    {
        return static_cast<PageSwitcherButton*>(buttons->child_at(index));
    }

} // namespace

PageSwitcher::PageSwitcher(PaginationModel* model)
    : model_(model)
    , buttons_(new views::View)
{
    AddChildView(buttons_);

    TotalPagesChanged();
    SelectedPageChanged(-1, model->selected_page());
    model_->AddObserver(this);
}

PageSwitcher::~PageSwitcher()
{
    model_->RemoveObserver(this);
}

int PageSwitcher::GetPageForPoint(const gfx::Point& point) const
{
    if (!buttons_->bounds().Contains(point))
        return -1;

    gfx::Point buttons_point(point);
    views::View::ConvertPointToTarget(this, buttons_, &buttons_point);

    for (int i = 0; i < buttons_->child_count(); ++i) {
        const views::View* button = buttons_->child_at(i);
        if (button->bounds().Contains(buttons_point))
            return i;
    }

    return -1;
}

void PageSwitcher::UpdateUIForDragPoint(const gfx::Point& point)
{
    int page = GetPageForPoint(point);

    const int button_count = buttons_->child_count();
    if (page >= 0 && page < button_count) {
        PageSwitcherButton* button = static_cast<PageSwitcherButton*>(buttons_->child_at(page));
        button->SetState(views::CustomButton::STATE_HOVERED);
        return;
    }

    for (int i = 0; i < button_count; ++i) {
        PageSwitcherButton* button = static_cast<PageSwitcherButton*>(buttons_->child_at(i));
        button->SetState(views::CustomButton::STATE_NORMAL);
    }
}

gfx::Size PageSwitcher::GetPreferredSize() const
{
    // Always return a size with correct height so that container resize is not
    // needed when more pages are added.
    return gfx::Size(buttons_->GetPreferredSize().width(),
        kPreferredHeight);
}

void PageSwitcher::Layout()
{
    gfx::Rect rect(GetContentsBounds());

    CalculateButtonWidthAndSpacing(rect.width());

    // Makes |buttons_| horizontally center and vertically fill.
    gfx::Size buttons_size(buttons_->GetPreferredSize());
    gfx::Rect buttons_bounds(rect.CenterPoint().x() - buttons_size.width() / 2,
        rect.y(),
        buttons_size.width(),
        rect.height());
    buttons_->SetBoundsRect(gfx::IntersectRects(rect, buttons_bounds));
}

void PageSwitcher::CalculateButtonWidthAndSpacing(int contents_width)
{
    const int button_count = buttons_->child_count();
    if (!button_count)
        return;

    contents_width -= 2 * kButtonStripPadding;

    int button_width = kMinButtonWidth;
    int button_spacing = kMinButtonSpacing;
    if (button_count > 1) {
        button_spacing = (contents_width - button_width * button_count) / (button_count - 1);
        button_spacing = std::min(kMaxButtonSpacing,
            std::max(kMinButtonSpacing, button_spacing));
    }

    button_width = (contents_width - (button_count - 1) * button_spacing) / button_count;
    button_width = std::min(kMaxButtonWidth,
        std::max(kMinButtonWidth, button_width));

    buttons_->SetLayoutManager(new views::BoxLayout(
        views::BoxLayout::kHorizontal, kButtonStripPadding, 0, button_spacing));
    for (int i = 0; i < button_count; ++i) {
        PageSwitcherButton* button = static_cast<PageSwitcherButton*>(buttons_->child_at(i));
        button->set_button_width(button_width);
    }
}

void PageSwitcher::ButtonPressed(views::Button* sender,
    const ui::Event& event)
{
    for (int i = 0; i < buttons_->child_count(); ++i) {
        if (sender == static_cast<views::Button*>(buttons_->child_at(i))) {
            model_->SelectPage(i, true /* animate */);
            break;
        }
    }
}

void PageSwitcher::TotalPagesChanged()
{
    buttons_->RemoveAllChildViews(true);
    for (int i = 0; i < model_->total_pages(); ++i) {
        PageSwitcherButton* button = new PageSwitcherButton(this);
        button->SetSelectedRange(i == model_->selected_page() ? 1 : 0);
        buttons_->AddChildView(button);
    }
    buttons_->SetVisible(model_->total_pages() > 1);
    Layout();
}

void PageSwitcher::SelectedPageChanged(int old_selected, int new_selected)
{
    if (old_selected >= 0 && old_selected < buttons_->child_count())
        GetButtonByIndex(buttons_, old_selected)->SetSelectedRange(0);
    if (new_selected >= 0 && new_selected < buttons_->child_count())
        GetButtonByIndex(buttons_, new_selected)->SetSelectedRange(1);
}

void PageSwitcher::TransitionStarted()
{
}

void PageSwitcher::TransitionChanged()
{
    const int current_page = model_->selected_page();
    const int target_page = model_->transition().target_page;

    double progress = model_->transition().progress;
    double remaining = progress - 1;

    if (current_page > target_page) {
        remaining = -remaining;
        progress = -progress;
    }

    GetButtonByIndex(buttons_, current_page)->SetSelectedRange(remaining);
    if (model_->is_valid_page(target_page))
        GetButtonByIndex(buttons_, target_page)->SetSelectedRange(progress);
}

} // namespace app_list
